<template>
  <div class="upload-file">
    <el-dialog v-model="dialogVisible" class="review-dialog" width="615" :show-close="false"
      :close-on-click-modal="false" :close-on-press-escape="false">
      <div class="header">
        <div class="title">分片上传</div>
        <div class="header-right">
          <div class="close" @click="close">
            <i class="iconfont icon-chahao1 close-dialog"></i>
          </div>
        </div>
      </div>
      <div class="content">
        <el-upload class="upload-demo" v-model:file-list="elFileList" drag :before-upload="beforeUpload"
          :show-file-list="false" action="#" multiple>
          <i class="iconfont icon-shangchuan"></i>
          <div class="el-upload__text">
            支持{{ getUploadType.join("、") }}，文件大小不能超过1.5GB
          </div>
        </el-upload>
        <div class="upload-list">
          <div class="uploading" v-for="uploadFile in uploadFileList">
            <div class="file-info">
              <div class="info-left">
                <div class="fileName">{{ uploadFile.name }}</div>
                <div class="fileSize">{{ formatSize(uploadFile.size) }}</div>
              </div>
              <div class="info-right">
                <div class="btn primary" @click="reUpload(uploadFile)"
                  v-if="uploadFile.isError && uploadFile.isUpoading">重试</div>
                <el-popconfirm class="box-item" title="确定删除上传吗？" placement="bottom" @confirm="deleteUpload(uploadFile)">
                  <template #reference>
                    <div class="btn danger">删除</div>
                  </template>
                </el-popconfirm>
              </div>
            </div>

            <div class="parse">
              <span>解析进度： </span>
              <el-progress :text-inside="true" :stroke-width="16" :percentage="uploadFile.parsePercentage">
              </el-progress>
            </div>
            <div class="progress">
              <span>上传进度：</span>

              <el-progress :text-inside="true" :stroke-width="16" :percentage="uploadFile.uploadPercentage">
              </el-progress>
            </div>
          </div>
        </div>
      </div>
      <div class="footer">
        <div class="btn confirm" @click="confirm">确定</div>
        <div class="btn cancel" @click="cancel">取消</div>
      </div>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive, onMounted, watch, computed, inject, toRefs } from "vue";
import { useRoute, useRouter } from "vue-router";
import {
  calculateFileHash,
  formatSize,
  getFileChunks,
  generateFileChunks,
} from "@/utils/global.js";
import {
  initFileAPI,
  uploadFileAPI,
  mergeFileAPI,
} from "@/api/fileAPI/fileAPI.js";
import { ElMessage, ElMessageBox } from "element-plus";

const props = defineProps({
  uploadType: {
    type: Array,
    default: () => {
      return ['JSON'];
    },
  },
  uploadNum: {
    type: Number,
    default: () => {
      return 1
    }
  }
});
const { uploadType } = toRefs(props);

const emit = defineEmits(["uploadAllFinish"]);

const uploadFileList = ref([]);
const elFileList = ref([])
let deboundTime = null;
const dialogVisible = ref(false)
const activeUploads = new Map();

const getUploadType = computed(() => {
  return props.uploadType;
});

const open = () => {
  dialogVisible.value = true
}
const close = () => {
  dialogVisible.value = false
  elFileList.value = []
  uploadFileList.value = []
  cancelAllUploads()
}
const confirm = () => {
  const unUpload = uploadFileList.value.find(item => !item.isUpoading)
  if (unUpload) {
    ElMessageBox.confirm(
      '还有未上传完成的文件，是否还要继续关闭？',
      {
        confirmButtonText: '确认',
        cancelButtonText: '取消',
        type: 'warning',
        center: true,
      }
    )
      .then(() => {
        emit('uploadAllFinish', uploadFileList.value)
        close()
      })
  } else {
    emit('uploadAllFinish', uploadFileList.value)
    close()
  }

}

const cancel = () => {
  if (uploadFileList.value.length > 0) {
    ElMessageBox.confirm(
      '已有上传完成或正在上传的文件，是否还要继续关闭？',
      {
        confirmButtonText: '确认',
        cancelButtonText: '取消',
        type: 'warning',
        center: true,
      }
    )
      .then(() => {
        close()
      })
  } else {
    close()
  }
}

const cancelAllUploads = () => {
  for (const [uid, task] of activeUploads) {
    task.abortController.abort(); // 终止上传
  }
  activeUploads.clear(); // 清空Map
}

// 修改后的deleteUpload函数
const deleteUpload = async (data) => {
  try {
    // 1. 从文件列表中删除
    const index = uploadFileList.value.findIndex(item => item.file.uid === data.file.uid);
    const elIndex = elFileList.value.findIndex(item => {
      return item.uid == data.file.uid
    });
    if (index !== -1) {
      uploadFileList.value.splice(index, 1);
    }
    if (elIndex !== -1) {
      elFileList.value.splice(elIndex, 1);
    }

    // 2. 检查是否有正在进行的上传任务
    const uploadTask = activeUploads.get(data.uid);
    if (uploadTask) {
      // 终止上传
      uploadTask.abortController.abort();

      ElMessage.success('上传已取消并删除');
      try {
        // 等待上传任务完全停止
        await uploadTask.uploadPromise;
        nextUpload()
      } catch (error) {
        // 预期中的取消错误，无需处理
        if (error.message !== '上传已被取消') {
          console.error('取消上传时发生错误:', error);
        }
      }
    } else {
      ElMessage.success('文件已删除');
      nextUpload()
    }
  } catch (error) {
    console.error('删除文件时出错:', error);
    ElMessage.error('删除文件时出错');
  }
};

//计算文件的md5值
const computeMd5 = async (file, uploadFile) => {
  await calculateFileHash(file, (progress) => {
    uploadFile.parsePercentage = progress;
  }).then((res) => {
    uploadFile.md5 = res;
  });
};

const beforeUpload = async (file) => {
  if (uploadFileList.value.length >= props.uploadNum) {
    ElMessage.error('已到最大上传数量')
    return false
  }
  if (file.size > 1024 * 1024 * 1024 * 1.5) {
    ElMessage.error("文件大小不能超过1.5GB");
    return false;
  }
  const extension = file.name.split(".").pop().toUpperCase();
  if (!getUploadType.value.includes(extension)) {
    ElMessage.error(
      `不支持的文件格式，请上传 ${getUploadType.value.join("、")} 格式的文件`
    );
    return false;
  }
  // 创建响应式对象
  const uploadFileData = reactive({
    name: file.name,
    size: file.size,
    parsePercentage: 0,
    uploadPercentage: 0,
    uploadSpeed: "0 M/s",
    file: file,
    uploadingStop: false,
    md5: "", // 确保初始化 md5 属性
    chunkCount: 0,
    uploadIndex: 0,
    uploadPromise: [],
    uploadFileChunk: [],
    fileType: file.type,
    ossName: "",
    isUpoading: false,
    isError: false,
    fileType: uploadType.value,
    otherName: ''
  });
  const fileName = file.name.split('.')
  const uploadFile = reactive({ ...uploadFileData });
  uploadFile.chunkCount = getFileChunks(file.size, 5);
  uploadFile.uploadFileChunk = generateFileChunks(file, 5);
  uploadFile.uploadFileData = fileName[fileName.length - 1]
  uploadFileList.value.push(uploadFile);
  await computeMd5(file, uploadFile);
  uploadFile.otherName = uploadFile.md5 + '.' + fileName[fileName.length - 1]
  nextUpload()
  return false
};

const nextUpload = () => {
  // 检查当前是否有文件正在上传（通过线程池或上传状态）
  const isAnyFileUploading =
    Array.from(activeUploads.values()).some(task =>
      !task.abortController.signal.aborted &&
      task.uploadPromise &&
      !task.uploadPromise.isFulfilled
    ) ||
    uploadFileList.value.some(file => file.atUploading);

  console.log(isAnyFileUploading, Array.from(activeUploads.values()).some(task =>
    !task.abortController.signal.aborted &&
    task.uploadPromise &&
    !task.uploadPromise.isFulfilled
  ), uploadFileList.value.some(file => file.atUploading));


  if (isAnyFileUploading) {
    console.log('检测到存在上传中的文件，等待当前上传完成');
    return;
  }

  // 找到第一个未完成上传的文件（按队列顺序）
  const nextFile = uploadFileList.value.find(
    file => file.uploadPercentage < 100 && !file.isError
  );

  if (nextFile) {
    console.log(`开始上传下一个文件: ${nextFile.name}`);
    upload(nextFile);
  } else {
    console.log('没有待上传的文件');
  }
};

const reUpload = async (uploadFile) => {
  if (deboundTime) {
    clearTimeout(deboundTime);
    deboundTime = null;
  }
  deboundTime = setTimeout(async () => {
    if (uploadFile.isError) {
      uploadFile.isError = false;
      uploadFile.uploadPercentage = 0;
      uploadFile.uploadPromise = [];
      uploadFile.isUpoading = false;
      await upload(uploadFile);
    }
  }, 400);
};

const upload = async (file) => {
  if (!file) {
    throw new Error("file参数不能为空");
  }
  const uploadFile = uploadFileList.value.find(
    (item) => item.file.uid === file.file.uid
  );
  uploadFile.isUpoading = false
  file.atUploading = true;  // 新增：立即标记为上传中
  uploadFile.isError = false
  if (!uploadFile) {
    throw new Error("未找到对应的上传文件");
  }

  const postData = {
    fileHash: uploadFile.md5,
    size: uploadFile.size,
    chunkCount: uploadFile.chunkCount,
    chunkSize: 1024 * 1024 * 5, // 5MB
    fileName: uploadFile.otherName,
  };

  try {
    const response = await initFileAPI(postData);
    const { code, data, msg } = response.data;

    if (code !== 200) {
      throw new Error(msg || "初始化文件上传失败");
    }

    // 转换URL数组格式，确保index是数字类型并按顺序排序
    const urlArray = Object.entries(data)
      .map(([key, value]) => ({
        index: parseInt(key) - 1, // 转换为从0开始的索引
        url: value,
      }))
      .sort((a, b) => a.index - b.index);

    // 更新上传文件信息
    uploadFile.uploadPromise = urlArray;
    uploadFile.uploadIndex = 0;

    // 开始上传（只上传后端返回的那些分片）
    return await uploadChunk(uploadFile);
  } catch (err) {
    ElMessage.error(`上传失败: ${err.message}`);
    nextUpload()
    uploadFile.isUpoading = true;
    uploadFile.atUploading = false;
    uploadFile.isError = true;
    throw err;
  }
};

const uploadChunk = async (uploadFile, maxConcurrent = 8) => {
  if (!uploadFile) {
    throw new Error("uploadFile参数不能为空");
  }

  // 创建AbortController用于取消上传（新增）
  const abortController = new AbortController();

  // 存储上传任务（新增）
  activeUploads.set(uploadFile.uid, {
    abortController,
    uploadPromise: null // 稍后存储Promise
  });

  const needUploadCount = uploadFile.uploadPromise
    .map((item) => {
      return item.index;
    })
    .map((item) => {
      return uploadFile.uploadFileChunk[item];
    })
    .map((item) => {
      return item.size;
    }).length;

  // 初始化上传状态
  const uploadedChunks = new Set(uploadFile.uploadedChunks || []);
  let uploadedBytes = 0;

  // 带重试机制的分片上传核心方法
  const uploadChunkTask = async (chunkInfo, retryCount = 0) => {
    // 检查是否已取消（新增）
    if (abortController.signal.aborted) {
      throw new Error('上传已被取消');
    }

    const { index: chunkIndex, url } = chunkInfo;

    if (uploadedChunks.has(chunkIndex)) {
      return;
    }

    const chunkData = uploadFile.uploadFileChunk?.[chunkIndex];
    if (!chunkData) {
      throw new Error(`分片${chunkIndex}数据缺失`);
    }

    // 准备二进制数据
    let binaryData;
    if (chunkData instanceof Blob) {
      binaryData = chunkData;
    } else if (chunkData instanceof ArrayBuffer) {
      binaryData = new Uint8Array(chunkData);
    } else if (chunkData instanceof Uint8Array) {
      binaryData = chunkData;
    } else {
      const blob = new Blob([chunkData]);
      binaryData = await blob.arrayBuffer();
    }

    try {
      // 尝试上传分片（带重试逻辑）
      await retryableUpload(
        `${import.meta.env.VITE_APP_URL}/${url}`,
        binaryData,
        chunkIndex,
        retryCount
      );

      // 更新上传状态
      uploadedBytes += chunkData.byteLength || 0;
      uploadedChunks.add(chunkIndex);
      uploadFile.uploadedChunks = Array.from(uploadedChunks);
      uploadFile.uploadPercentage = Math.ceil(
        (uploadedChunks.size / needUploadCount) * 100
      );
    } catch (error) {
      // 如果是取消导致的错误，直接抛出（新增）
      if (error.name === 'AbortError' || error.message === '上传已被取消') {
        throw error;
      }

      uploadFile.isError = true;
      ElMessage.error(`分片 ${chunkIndex + 1} 上传失败: ${error.message}`);
      throw error;
    }
  };

  // 带重试的上传函数
  const retryableUpload = async (url, data, chunkIndex, retryCount = 0) => {
    const maxRetries = 3;
    const baseDelay = 1000; // 基础延迟1秒

    try {
      // 传递AbortSignal给API调用（修改）
      return await uploadFileAPI(url, data, { signal: abortController.signal });
    } catch (error) {
      // 如果是取消导致的错误，直接抛出（新增）
      if (error.name === 'AbortError') {
        throw new Error('上传已被取消');
      }

      if (retryCount < maxRetries - 1) {
        const delay = baseDelay * Math.pow(2, retryCount); // 指数退避
        await new Promise((resolve) => setTimeout(resolve, delay));
        return retryableUpload(url, data, chunkIndex, retryCount + 1);
      }
      throw error; // 重试次数用尽，抛出错误
    }
  };

  // 分片上传控制流程
  try {
    // 使用后端返回的URL数组作为待上传队列
    const pendingChunks = uploadFile.uploadPromise || [];

    // 并发控制实现
    const concurrencyController = async (tasks, concurrency) => {
      const results = [];
      const executing = new Set();

      for (const task of tasks) {
        // 检查是否已取消（新增）
        if (abortController.signal.aborted) {
          throw new Error('上传已被取消');
        }

        const p = Promise.resolve().then(() => uploadChunkTask(task));
        results.push(p);
        executing.add(p);

        const clean = () => executing.delete(p);
        p.then(clean).catch(clean);

        if (executing.size >= concurrency) {
          await Promise.race(executing);
        }
      }

      return Promise.all(results);
    };

    if (pendingChunks.length === 0) {
      uploadFile.uploadPercentage = 100;
    }

    // 执行上传并等待所有分片完成
    const uploadPromise = concurrencyController(pendingChunks, maxConcurrent);
    // 存储Promise（新增）
    activeUploads.get(uploadFile.uid).uploadPromise = uploadPromise;

    await uploadPromise;

    // 如果已取消，不再继续后续操作（新增）
    if (abortController.signal.aborted) {
      return {
        success: false,
        cancelled: true,
        uploadedBytes,
        uploadedCount: uploadedChunks.size
      };
    }

    uploadFile.isUpoading = true;

    // 只有在所有分片都上传成功后才执行合并
    if (uploadedChunks.size == needUploadCount) {
      // 执行合并
      const response = await mergeFileAPI({
        fileHash: uploadFile.md5,
      });
      const { code, data, msg } = response.data;
      if (code == 200) {
        uploadFile.ossName = data;
      }
    } else {
      throw new Error(
        `上传不完整，已上传${uploadedChunks.size}个分片，总共需要${uploadFile.chunkCount}个分片`
      );
    }

    return {
      success: true,
      uploadedBytes,
      uploadedCount: uploadedChunks.size,
      totalCount: uploadFile.chunkCount,
    };
  } catch (error) {
    if (error.message === '上传已被取消') {
      console.log('上传已主动取消');
      return {
        success: false,
        cancelled: true,
        uploadedBytes,
        uploadedCount: uploadedChunks.size
      };
    }
    console.error("上传流程异常:", error);
    throw error;
  } finally {
    uploadFile.atUploading = false;  // 新增：重置上传状态
    // 无论成功或失败，都从活动任务中移除（新增）
    activeUploads.delete(uploadFile.uid);
    nextUpload()
  }
};

defineExpose({
  open
})
</script>
<style lang="less" scoped>
.upload-file {
  .header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 10px;

    .title {
      line-height: 33px;
      font-weight: 700;
      font-size: 24px;
      color: #222;
    }

    .header-right {
      display: flex;
      align-items: center;
      justify-content: center;
      margin-top: 4px;

      .close {
        margin-left: 10px;

        .close-dialog {
          cursor: pointer;
          font-size: 34px;
          color: #8c8c8c;
          transition: all 0.3s ease-in-out;

          &:hover {
            color: #8c8c8cbd;
          }
        }
      }
    }

  }

  .iconfont {
    font-size: 40px;
    margin-bottom: 20px;
  }

  .upload-list {
    height: 290px;
    overflow: auto;
    scrollbar-width: none;
    /* firefox */
    -ms-overflow-style: none;
    /* IE 10+ */
    overflow-x: hidden;
    overflow-y: auto;

    .uploading {
      margin-top: 10px;

      .file-info {
        display: flex;
        align-items: center;
        justify-content: space-between;

        .info-left {
          display: flex;
          align-items: center;

          .fileName {
            font-size: 16px;
            color: #555;
            margin-right: 10px;
          }

          .fileSize {
            font-size: 14px;
            color: #8c8c8c;
          }
        }

        .info-right {
          display: flex;
          align-items: center;
          flex-wrap: wrap;
          gap: 10px;

          .btn {
            color: #fff;
            padding: 5px 10px;
            border-radius: 4px;
            cursor: pointer;
            transition: all 0.3s ease-in-out;

            &:hover {
              opacity: 0.8;
            }
          }

          .primary {
            background-color: #409eff;
          }

          .danger {
            background-color: #ff4040;
          }
        }
      }
    }
  }

  .footer {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
    gap: 10px;

    .btn {
      width: 100px;
      height: 32px;
      font-size: 15px;
      color: #fff;
      border-radius: 4px;
      display: flex;
      align-items: center;
      justify-content: center;
      transition: all 0.3s ease-in-out;
      cursor: pointer;

      &:hover {
        opacity: 0.8;
      }
    }

    .confirm {
      background-color: #5f47ea;
    }

    .cancel {
      background-color: #f43c2f;
    }
  }
}
</style>
